package com.opencee.cloud.autoconfigure.web;

import cn.hutool.crypto.SecureUtil;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.opencee.boot.autoconfigure.amqp.autoconfigure.AmqpConstants;
import com.opencee.common.utils.SpringContextHolder;
import com.google.common.collect.Maps;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.SwaggerDefinition;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.context.ApplicationContext;
import org.springframework.core.MethodParameter;
import org.springframework.core.env.Environment;
import org.springframework.http.MediaType;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition;
import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import springfox.documentation.annotations.ApiIgnore;

import java.util.*;

/**
 * 自定义注解扫描
 *
 * @author liuyadu
 */
@Slf4j
public class RequestMappingScan implements ApplicationRunner {

    private AntPathMatcher pathMatcher = new AntPathMatcher();

    private List<String> ignoreList = new ArrayList<>(Arrays.asList("/error", "/swagger/**", "/swagger-resources/**"));

    private Boolean ignore(String path) {
        return ignoreList.stream().anyMatch(pattern -> pathMatcher.match(pattern, path));
    }

    private String getMediaTypes(Set<MediaType> mediaTypes) {
        StringBuilder sbf = new StringBuilder();
        for (MediaType mediaType : mediaTypes) {
            sbf.append(mediaType.toString()).append(",");
        }
        if (mediaTypes.size() > 0) {
            sbf.deleteCharAt(sbf.length() - 1);
        }
        return sbf.toString();
    }

    private String getMethods(Set<RequestMethod> requestMethods) {
        StringBuilder sbf = new StringBuilder();
        for (RequestMethod requestMethod : requestMethods) {
            sbf.append(requestMethod.toString()).append(",");
        }
        if (requestMethods.size() > 0) {
            sbf.deleteCharAt(sbf.length() - 1);
        }
        return sbf.toString();
    }

    private String getUrls(Set<String> urls) {
        StringBuilder sbf = new StringBuilder();
        for (String url : urls) {
            sbf.append(url).append(",");
        }
        if (urls.size() > 0) {
            sbf.deleteCharAt(sbf.length() - 1);
        }
        return sbf.toString();
    }


    @Override
    public void run(ApplicationArguments args) throws Exception {

        ApplicationContext applicationContext = SpringContextHolder.getApplicationContext();
        Environment environment = applicationContext.getEnvironment();
        AmqpTemplate amqpTemplate = applicationContext.getBean(AmqpTemplate.class);
        if (amqpTemplate == null) {
            return;
        }
        // 服务名称
        String serviceId = environment.getProperty("spring.application.name", "application");
        // 所有接口映射
        RequestMappingHandlerMapping mapping = applicationContext.getBean(RequestMappingHandlerMapping.class);
        // 获取url与类和方法的对应信息
        Map<RequestMappingInfo, HandlerMethod> map = mapping.getHandlerMethods();
        Map<String, Object> apiInfo = applicationContext.getBeansWithAnnotation(SwaggerDefinition.class);
        String basePath = null;
        String env = environment.getProperty("spring.profiles.active");
        String title = "";
        String description = "";
        String termsOfServiceUrl = "";
        String version = "";
        SwaggerDefinition swaggerDefinition = null;
        if (apiInfo != null && !apiInfo.isEmpty()) {
            for (Map.Entry<String, Object> entry : apiInfo.entrySet()) {
                Class<?> clazz = entry.getValue().getClass();
                if (clazz.isAnnotationPresent(SwaggerDefinition.class)) {
                    // 查询 "类" 上的注解
                    swaggerDefinition = clazz.getAnnotation(SwaggerDefinition.class);
                }
            }
        }
        if (swaggerDefinition != null) {
            basePath = swaggerDefinition.basePath();
            title = swaggerDefinition.info().title();
            description = swaggerDefinition.info().description();
            termsOfServiceUrl = swaggerDefinition.info().termsOfService();
            version = swaggerDefinition.info().version();
        }
        if (StringUtils.isEmpty(basePath)) {
            basePath = "/";
        }
        List<Map<String, String>> list = new ArrayList<Map<String, String>>();
        TreeSet<String> groups = new TreeSet<>();
        for (Map.Entry<RequestMappingInfo, HandlerMethod> m : map.entrySet()) {
            RequestMappingInfo info = m.getKey();
            HandlerMethod method = m.getValue();
            if (method.getMethodAnnotation(ApiIgnore.class) != null) {
                // 忽略的接口不扫描
                continue;
            }
            Set<MediaType> mediaTypeSet = info.getProducesCondition().getProducibleMediaTypes();
            for (MethodParameter params : method.getMethodParameters()) {
                if (params.hasParameterAnnotation(RequestBody.class)) {
                    mediaTypeSet.add(MediaType.APPLICATION_JSON);
                    break;
                }
            }
            String mediaTypes = getMediaTypes(mediaTypeSet);
            // 请求类型
            RequestMethodsRequestCondition methodsCondition = info.getMethodsCondition();
            String methods = getMethods(methodsCondition.getMethods());
            // 请求路径
            PatternsRequestCondition p = info.getPatternsCondition();
            String urls = getUrls(p.getPatterns());
            // 跳过默认忽略接口
            if (ignore(urls)) {
                continue;
            }
            Map<String, String> api = Maps.newHashMap();
            // 类名
            String className = method.getMethod().getDeclaringClass().getName();
            Api apiAnnotation = method.getMethod().getDeclaringClass().getAnnotation(Api.class);
            String group = method.getMethod().getDeclaringClass().getSimpleName();
            if (apiAnnotation != null) {
                String[] tags = apiAnnotation.tags();
                if (tags.length > 0) {
                    group = tags[0];
                }
            }
            groups.add(group);
            // 方法名
            String methodName = method.getMethod().getName();
            // md5码
            String md5 = SecureUtil.md5(serviceId + urls);
            String name = "";
            String remark = "";

            ApiOperation apiOperation = method.getMethodAnnotation(ApiOperation.class);
            if (apiOperation != null) {
                name = apiOperation.value();
                remark = apiOperation.notes();
            }
            name = StringUtils.isEmpty(name) ? methodName : name;
            api.put("name", name);
            api.put("code", md5);
            api.put("remark", remark);
            api.put("path", urls);
            api.put("basePath", basePath);
            api.put("className", className);
            api.put("methodName", methodName);
            api.put("requestMethod", methods);
            api.put("group", group);
            api.put("sid", serviceId);
            api.put("contentType", mediaTypes);
            list.add(api);
        }

        list.sort(Comparator.comparing(o -> o.get("name")));

        TreeMap<String, Object> scan = new TreeMap<>();
        scan.put("title", title);
        scan.put("basePath", basePath);
        scan.put("description", description);
        scan.put("version", version);
        scan.put("termsOfServiceUrl", termsOfServiceUrl);
        scan.put("env", env);
        scan.put("serviceId", serviceId);
        scan.put("groups", groups);
        scan.put("mapping", list);
        String md5 = SecureUtil.md5(JSONObject.toJSONString(scan, SerializerFeature.SortField));
        scan.put("md5", md5);
        String message = JSONObject.toJSONString(scan, SerializerFeature.SortField);
        log.debug("ApiInfo:{}", message);
        try {
            // 发送mq扫描消息
            amqpTemplate.convertAndSend(AmqpConstants.TOPIC_EXCHANGE, AmqpConstants.TOPIC_API_SCAN_ROUTER_KEY, message);
        } catch (Exception e) {
            log.error("发送失败:{}", e);
        }
    }
}
